package hc

import (
	"strconv"
	"time"

	"github.com/gomodule/redigo/redis"
)

// ErrNil reids的ErrNil
var ErrNil = redis.ErrNil

// RedisPool redis连接池
type RedisPool struct {
	Pool *redis.Pool
}

// RedisArgs redis.Args类型的别名
type RedisArgs = redis.Args

// Redis 存放redis连接池的变量
var Redis *RedisPool

// RedisConn Redis连接
func RedisConn(host, port, password string, maxIDle, maxActive int) error {
	Redis = &RedisPool{}
	return Redis.initPool(host, port, password, maxIDle, maxActive)
}

// InitPool 新建连接池
func (p *RedisPool) initPool(host, port, password string, maxIDle, maxActive int) error {
	p.Pool = &redis.Pool{
		MaxIdle:     maxIDle,           // 最大空闲链接
		MaxActive:   maxActive,         // 最大链接
		IdleTimeout: 240 * time.Second, // 空闲链接超时
		Wait:        true,              // 当连接池耗尽时,是否阻塞等待
		Dial: func() (redis.Conn, error) {
			// password 没有密码时password使用空字符串
			c, err := redis.Dial("tcp", host+":"+port, redis.DialPassword(password))
			if err != nil {
				return nil, err
			}
			return c, nil
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error { // 健康检查,如果ping出错了，就关闭这个redis连接
			if time.Since(t) < time.Minute { // 每分钟定时检查
				return nil
			}
			_, err := c.Do("PING")
			return err
		},
	}

	// 检查redis是否连接有误
	_, err := p.Pool.Dial()
	return err
}

// do 执行redis命令
func (p *RedisPool) do(command string, args ...interface{}) (interface{}, error) {
	conn := p.Pool.Get()
	defer conn.Close()
	return conn.Do(command, args...)
}

// EXPIRE 设置key的有效期
func (p *RedisPool) EXPIRE(key string, seconds int) error {
	_, err := p.do("EXPIRE", key, seconds)
	return err
}

// DEL 删除一个key
func (p *RedisPool) DEL(key string) error {
	_, err := p.do("DEL", key)
	return err
}

// EXISTS 检查一个建是否存在
func (p *RedisPool) EXISTS(key string) (bool, error) {
	exists, err := redis.Bool(p.do("EXISTS", key))
	return exists, err
}

//-------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------- string ----------------------------------------------------//

// GET 返回与键 key 相关联的字符串值。
func (p *RedisPool) GET(key string) (value string, err error) {
	value, err = redis.String(p.do("GET", key))
	return
}

// SET 将字符串值 value 关联到 key 。
// 如果 key 已经持有其他值， SET 就覆写旧值， 无视类型。  特别注意：无视类型覆盖
// 当 SET 命令对一个带有生存时间（TTL）的键进行设置之后， 该键原有的 TTL 将被清除。
func (p *RedisPool) SET(key, value string) error {
	_, err := p.do("SET", key, value)
	return err
}

// INCR 为键 key 储存的数字值加上一。
// 如果键 key 不存在， 那么它的值会先被初始化为 0 ， 然后再执行 INCR 命令。
// 如果键 key 储存的值不能被解释为数字， 那么 INCR 命令将返回一个错误。
// 本操作的值限制在 64 位(bit)有符号数字表示之内。
// 返回值: INCR 命令会返回键 key 在执行加一操作之后的值。
func (p *RedisPool) INCR(key string) (value int64, err error) {
	value, err = redis.Int64(p.do("INCR", key))
	return
}

// INCRBY 为键 key 储存的数字值加上增量 increment 。
// 如果键 key 不存在， 那么键 key 的值会先被初始化为 0 ， 然后再执行 INCRBY 命令。
// 如果键 key 储存的值不能被解释为数字， 那么 INCRBY 命令将返回一个错误。
// 本操作的值限制在 64 位(bit)有符号数字表示之内。
// 返回值: 在加上增量 increment 之后， 键 key 的值。
func (p *RedisPool) INCRBY(key string, increment int64) (value int64, err error) {
	value, err = redis.Int64(p.do("INCRBY", key, increment))
	return
}

// INCRBYFLOAT 为键 key 储存的值加上浮点数增量 increment 。
// 如果键 key 不存在， 那么 INCRBYFLOAT 会先将键 key 的值设为 0 ， 然后再执行加法操作。
// 如果命令执行成功， 那么键 key 的值会被更新为执行加法计算之后的新值， 并且新值会以字符串的形式返回给调用者。
// 无论加法计算所得的浮点数的实际精度有多长， INCRBYFLOAT 命令的计算结果最多只保留小数点的后十七位。
// 当以下任意一个条件发生时， 命令返回一个错误：
//     键 key 的值不是字符串类型(因为 Redis 中的数字和浮点数都以字符串的形式保存，所以它们都属于字符串类型）；
//     1.键 key 当前的值或者给定的增量 increment 不能被解释(parse)为双精度浮点数。
//
// 本操作的值限制在 64 位(bit)有符号数字表示之内。
// 返回值: 在加上增量 increment 之后， 键 key 的值。
func (p *RedisPool) INCRBYFLOAT(key string, increment float64) (value float64, err error) {
	value, err = redis.Float64(p.do("INCRBYFLOAT", key, increment))
	return
}

//-------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------- hash ----------------------------------------------------//
// HSET 作同时将多个 field-value (域-值)对设置到哈希表 key 中
// 此命令会覆盖哈希表中已存在的域
func (p *RedisPool) HSET(key string, fieldValue interface{}) (err error) {
	args := redis.Args{key}.AddFlat(fieldValue)
	_, err = p.do("HSET", args...)
	return
}

// HGET 返回哈希表中给定域的值。
func (p *RedisPool) HGET(key, field string) (value string, err error) {
	value, err = redis.String(p.do("HGET", key, field))
	return
}

// HGETALL 返回哈希表 key 中，所有的域和值,并赋值给out(out是一个结构体的指针)
// out 字段使用"redis" tag 作匹配
func (p *RedisPool) HGETALL(key string, out interface{}) (err error) {
	res, err := redis.Values(p.do("HGETALL", key))
	if err != nil {
		return
	}
	if len(res) == 0 {
		return
	}
	err = redis.ScanStruct(res, out)
	return
}

// HMGET 返回哈希表 key 中，一个或多个给定域的值。
// 如果给定的域不存在于哈希表，那么返回一个 nil 值。
// 因为不存在的 key 被当作一个空哈希表来处理，所以对一个不存在的 key 进行 HMGET 操作将返回一个只带有 nil 值的表。
func (p *RedisPool) HMGET(key string, fields ...string) (value map[string]string, err error) {
	args := redis.Args{key}
	for _, field := range fields {
		args = args.Add(field)
	}
	res, err := redis.Strings(p.do("HMGET", args...))
	if err != nil {
		return
	}
	value = make(map[string]string)
	for index, field := range fields {
		value[field] = res[index]
	}
	return
}

// HINCRBY 为哈希表 key 中的域 field 的值加上增量 increment 。
// 增量也可以为负数，相当于对给定域进行减法操作。
// 如果 key 不存在，一个新的哈希表被创建并执行 HINCRBY 命令。
// 如果域 field 不存在，那么在执行命令前，域的值被初始化为 0 。
// 对一个储存字符串值的域 field 执行 HINCRBY 命令将造成一个错误。
// 本操作的值被限制在 64 位(bit)有符号数字表示之内。
// 返回值: 执行 HINCRBY 命令之后，哈希表 key 中域 field 的值。
func (p *RedisPool) HINCRBY(key, field string, increment int64) (value int64, err error) {
	value, err = redis.Int64(p.do("HINCRBY", key, field, increment))
	return
}

// HINCRBYFLOAT 为哈希表 key 中的域 field 加上浮点数增量 increment 。
// 如果哈希表中没有域 field ，那么 HINCRBYFLOAT 会先将域 field 的值设为 0 ，然后再执行加法操作。
// 如果键 key 不存在，那么 HINCRBYFLOAT 会先创建一个哈希表，再创建域 field ，最后再执行加法操作。
// 无论加法计算所得的浮点数的实际精度有多长， INCRBYFLOAT 命令的计算结果最多只保留小数点的后十七位。
// 当以下任意一个条件发生时，返回一个错误：
//     1.域 field 的值不是字符串类型(因为 redis 中的数字和浮点数都以字符串的形式保存，所以它们都属于字符串类型）
//     2.域 field 当前的值或给定的增量 increment 不能解释(parse)为双精度浮点数
//
// 返回值: 执行加法操作之后 field 域的值。
func (p *RedisPool) HINCRBYFLOAT(key, field string, increment float64) (value float64, err error) {
	value, err = redis.Float64(p.do("HINCRBYFLOAT", key, field, increment))
	return
}

// HDEL 删除哈希表 key 中的一个或多个指定域，不存在的域将被忽略。
// 返回值: 被成功移除的域的数量，不包括被忽略的域。
func (p *RedisPool) HDEL(key string, fields ...string) (count int, err error) {
	args := redis.Args{key}
	for _, field := range fields {
		args = args.Add(field)
	}
	count, err = redis.Int(p.do("HDEL", args...))
	return
}

//-------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------- set ----------------------------------------------------//

// SADD 将一个或多个 member 元素加入到集合 key 当中，已经存在于集合的 member 元素将被忽略。
// 假如 key 不存在，则创建一个只包含 member 元素作成员的集合。
// 当 key 不是集合类型时，返回一个错误。
func (p *RedisPool) SADD(args ...interface{}) error {
	_, err := p.do("SADD", args...)
	return err
}

// SISMEMBER 判断 value 元素是否集合 key 的成员
// 如果 member 元素是集合的成员，返回 true 。 如果 member 元素不是集合的成员，或 key 不存在，返回 false 。
func (p *RedisPool) SISMEMBER(key string, value string) (bool, error) {
	exists, err := redis.Bool(p.do("SISMEMBER", key, value))
	return exists, err
}

// SREM 移除集合 key 中的一个或多个 member 元素，不存在的 member 元素会被忽略。
// 当 key 不是集合类型，返回一个错误。
func (p *RedisPool) SREM(key string, member ...interface{}) error {
	_, err := p.do("SREM", key, member)
	return err
}

// SCARD 返回集合 key 的基数(集合中元素的数量)。
// 集合的基数。 当 key 不存在时，返回 0 。
func (p *RedisPool) SCARD(key string) (score int, err error) {
	score, err = redis.Int(p.do("SCARD", key))
	return
}

// SMEMBERS 返回集合 key 中的所有成员。
// 不存在的 key 被视为空集合。
func (p *RedisPool) SMEMBERS(key string) ([]string, error) {
	result, err := redis.Strings(p.do("SMEMBERS", key))
	return result, err
}

//-------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------- zset ----------------------------------------------------//
// ZADD 将一个或多个 member 元素及其 score 值加入到有序集 key 当中。
// 如果某个 member 已经是有序集的成员，那么更新这个 member 的 score 值，并通过重新插入这个 member 元素，来保证该 member 在正确的位置上。
// score 值可以是整数值或双精度浮点数。
// 如果 key 不存在，则创建一个空的有序集并执行 ZADD 操作。
// 当 key 存在但不是有序集类型时，返回一个错误。
//
// 使用例子:
//
//
//  scoreMember := map[int]string{111: "a", 101: "b"}
// 	if err := conn.ZADD("testZSET", scoreMember); err != nil {
// 		t.Error(err)
//  }
//
func (p *RedisPool) ZADD(key string, scoreMember interface{}) error {
	args := redis.Args{key}.AddFlat(scoreMember)
	_, err := p.do("ZADD", args...)
	return err
}

// ZSCORE 返回有序集 key 中，成员 member 的 score 值。
// 如果 member 元素不是有序集 key 的成员，或 key 不存在，返回 nil 。
func (p *RedisPool) ZSCORE(key string, member interface{}) (score string, err error) {
	score, err = redis.String(p.do("ZSCORE", key, member))
	return
}

// ZINCRBY 为有序集 key 的成员 member 的 score 值加上增量 increment 。
// 可以通过传递一个负数值 increment ，让 score 减去相应的值，比如 ZINCRBY key -5 member ，就是让 member 的 score 值减去 5
// 当 key 不存在，或 member 不是 key 的成员时， ZINCRBY key increment member 等同于 ZADD key increment member 。
// 当 key 不是有序集类型时，返回一个错误。
// score 值可以是整数值或双精度浮点数。
func (p *RedisPool) ZINCRBY(key string, increment, member interface{}) (score string, err error) {
	score, err = redis.String(p.do("ZINCRBY", key, increment, member))
	return
}

// ZCARD 返回有序集 key 的基数(集合中元素的数量)。
// 当 key 存在且是有序集类型时，返回有序集的基数。 当 key 不存在时，返回 0 。
func (p *RedisPool) ZCARD(key string) (score int, err error) {
	score, err = redis.Int(p.do("ZCARD", key))
	return
}

// ZCOUNT 返回有序集 key 中， score 值在 min 和 max 之间(默认包括 score 值等于 min 或 max )的成员的数量。
// 返回值: score 值在 min 和 max 之间的成员的数量。
func (p *RedisPool) ZCOUNT(key string, min, max float64) (count int64, err error) {
	count, err = redis.Int64(p.do("ZCOUNT", key, min, max))
	return
}

// ZRANGE 返回有序集 key 中，指定区间内的成员。
// 其中成员的位置按 score 值递增(从小到大)来排序。
// 具有相同 score 值的成员按字典序(lexicographical order )来排列。
// 下标参数 start 和 stop 都以 0 为底，也就是说，以 0 表示有序集第一个成员，以 1 表示有序集第二个成员，以此类推。
// 你也可以使用负数下标，以 -1 表示最后一个成员， -2 表示倒数第二个成员，以此类推。
// 超出范围的下标并不会引起错误。
// 比如说，当 start 的值比有序集的最大下标还要大，或是 start > stop 时， ZRANGE 命令只是简单地返回一个空列表。
// 另一方面，假如 stop 参数的值比有序集的最大下标还要大，那么 Redis 将 stop 当作最大下标来处理。
// 返回值: 指定区间内的有序集成员的列表。
func (p *RedisPool) ZRANGE(key string, start, stop int64) (result []string, err error) {
	result, err = redis.Strings(p.do("ZRANGE", key, start, stop))
	return
}

// ZRANGEWITHSCORES 使用WITHSCORES选项的ZRANGE
func (p *RedisPool) ZRANGEWITHSCORES(key string, start, stop int64) (result []map[string]float64, err error) {
	values, err := redis.Values(p.do("ZRANGE", key, start, stop, "WITHSCORES"))
	if err != nil {
		return
	}
	if len(values) == 0 {
		result = make([]map[string]float64, 0, 1)
		return
	}
	length := len(values) / 2
	result = make([]map[string]float64, length)
	for i := 0; i < length; i++ {
		member := values[i*2].([]byte)
		m := string(member)
		score := values[i*2+1].([]byte)
		s, _ := strconv.ParseFloat(string(score), 64)
		item := map[string]float64{m: s}
		result[i] = item
	}
	return
}

// ZREVRANGE 返回有序集 key 中，指定区间内的成员。
// 其中成员的位置按 score 值递减(从大到小)来排序。
// 具有相同 score 值的成员按字典序的逆序(reverse lexicographical order)排列。
// 下标参数 start 和 stop 都以 0 为底，也就是说，以 0 表示有序集第一个成员，以 1 表示有序集第二个成员，以此类推。
// 你也可以使用负数下标，以 -1 表示最后一个成员， -2 表示倒数第二个成员，以此类推。
// 超出范围的下标并不会引起错误。
// 返回值: 指定区间内的有序集成员的列表。
func (p *RedisPool) ZREVRANGE(key string, start, stop int64) (result []string, err error) {
	result, err = redis.Strings(p.do("ZREVRANGE", key, start, stop))
	return
}

// ZREVRANGEWITHSCORES 使用WITHSCORES选项的ZREVRANGE
func (p *RedisPool) ZREVRANGEWITHSCORES(key string, start, stop int64) (result []map[string]float64, err error) {
	values, err := redis.Values(p.do("ZREVRANGE", key, start, stop, "WITHSCORES"))
	if err != nil {
		return
	}
	if len(values) == 0 {
		result = make([]map[string]float64, 0, 1)
		return
	}
	length := len(values) / 2
	result = make([]map[string]float64, length)
	for i := 0; i < length; i++ {
		member := values[i*2].([]byte)
		m := string(member)
		score := values[i*2+1].([]byte)
		s, _ := strconv.ParseFloat(string(score), 64)
		item := map[string]float64{m: s}
		result[i] = item
	}
	return
}

// ZRANK 返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递增(从小到大)顺序排列。
// 排名以 0 为底，也就是说， score 值最小的成员排名为 0 。
// 使用 ZREVRANK key member 命令可以获得成员按 score 值递减(从大到小)排列的排名。
// 返回值:
//  如果 member 是有序集 key 的成员，返回 member 的排名。
//  如果 member 不是有序集 key 的成员，返回 -1。
func (p *RedisPool) ZRANK(key string, member interface{}) (rank int64, err error) {
	rank, err = redis.Int64(p.do("ZRANK", key, member))
	if err != nil && err == redis.ErrNil {
		rank = -1
	}
	return
}

// ZREVRANK 返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递减(从大到小)顺序排列。
// 排名以 0 为底，也就是说， score 值最小的成员排名为 0 。
// 返回值:
//
//  * 如果 member 是有序集 key 的成员，返回 member 的排名。
//  * 如果 member 不是有序集 key 的成员，返回 -1。
//
func (p *RedisPool) ZREVRANK(key string, member interface{}) (rank int64, err error) {
	rank, err = redis.Int64(p.do("ZREVRANK", key, member))
	if err != nil && err == redis.ErrNil {
		rank = -1
	}
	return
}

// ZREM 移除有序集 key 中的一个或多个成员，不存在的成员将被忽略。
func (p *RedisPool) ZREM(key string, member ...interface{}) (count int64, err error) {
	args := redis.Args{key}
	for _, field := range member {
		args = args.Add(field)
	}
	count, err = redis.Int64(p.do("ZREM", args...))
	return
}

// ZREMRANGEBYRANK 移除有序集key中，指定排名(rank)区间 start 和 stop 内的所有成员。
// 下标参数start和stop都是从0开始计数，0是分数最小的那个元素。
// 索引也可是负数，表示位移从最高分处开始数。
// 例如，-1是分数最高的元素，-2是分数第二高的，依次类推。
func (p *RedisPool) ZREMRANGEBYRANK(key string, start, stop int64) (count int64, err error) {
	count, err = redis.Int64(p.do("ZREMRANGEBYRANK", key, start, stop))
	return
}

// // Bool  执行redis操作返回布尔值
// func (p *RedisPool) Bool(command string, args ...interface{}) (bool, error) {
// 	return redis.Bool(p.do(command, args...))
// }

// // Float64  执行redis操作返回float64
// func (p *RedisPool) Float64(command string, args ...interface{}) (float64, error) {
// 	return redis.Float64(p.do(command, args...))
// }

// // Float64s  执行redis操作返回float64数组
// func (p *RedisPool) Float64s(command string, args ...interface{}) ([]float64, error) {
// 	return redis.Float64s(p.do(command, args...))
// }

// // String  执行redis命令string
// func (p *RedisPool) String(command string, args ...interface{}) (string, error) {
// 	return redis.String(p.do(command, args...))
// }

// // Strings  执行redis命令string数组
// func (p *RedisPool) Strings(command string, args ...interface{}) ([]string, error) {
// 	return redis.Strings(p.do(command, args...))
// }

// // Int  执行redis命令int
// func (p *RedisPool) Int(command string, args ...interface{}) (int, error) {
// 	return redis.Int(p.do(command, args...))
// }

// // Ints  执行redis命令int数组
// func (p *RedisPool) Ints(command string, args ...interface{}) ([]int, error) {
// 	return redis.Ints(p.do(command, args...))
// }

// // StringMap 执行redis命令StringMap
// func (p *RedisPool) StringMap(command string, args ...interface{}) (map[string]string, error) {
// 	return redis.StringMap(p.do(command, args...))
// }

// // ScanStruct 执行redis命令StringMap
// // 字段tag使用redis,当redis:"-"则忽略
// func (p *RedisPool) ScanStruct(command string, dest interface{}, args ...interface{}) error {
// 	values, err := redis.Values(p.do(command, args...))
// 	if err != nil {
// 		return err
// 	}
// 	err = redis.ScanStruct(values, dest)
// 	return err
// }
